﻿using System;
using System.IO;

namespace MyUtils
{
	public sealed class SeparateDomain : IDisposable, IHideObjectMembers
	{
		static int domainNameSeed;

		AppDomain _domain;

		public AppDomain Domain
		{
			get
			{
				// TODO sync?
				return _domain ?? (_domain = createDomain());
			}
		}

		#region IDisposable Members

		///<summary>
		///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		///</summary>
		///<filterpriority>2</filterpriority>
		public void Dispose()
		{
			ReleaseDomain();
		}

		#endregion

		static AppDomain createDomain()
		{
			// todo configure good path! not current dir and not path of MyUtils.dll
			var dom = AppDomain.CreateDomain("MyUtils Separate Domain " + ++domainNameSeed, null, new AppDomainSetup
			{
				ApplicationBase = Path.GetDirectoryName(typeof(SeparateDomain).Assembly.Location),//Directory.GetCurrentDirectory(),
				//PrivateBinPath = Directory.GetCurrentDirectory(),
			});
			// TODO configure evidence, contexts, etc
			return dom;
		}

		public TResult Execute<T, TResult>(Func<T, TResult> action) where T : MarshalByRefObject
		{
			if (typeof(TResult).Attribute<SerializableAttribute>() == null)
			{
				throw new ArgumentException("Return type must be marked as Serializable");
			}
			var runnerType = typeof(T);
			var runnerAsm = runnerType.Assembly.FullName;
			var runnerTypeName = runnerType.FullName;
			var remoteClass = (T)Domain.CreateInstanceAndUnwrap(runnerAsm, runnerTypeName);
			return action(remoteClass);
		}

		// TODO call from destructor?

		void ReleaseDomain()
		{
			AppDomain.Unload(Domain);
			_domain = null;
		}
	}
}